A framework for predicting across multiple models

1 Overview and Usage

This notebook provides inference for PartAn data, and uses your built models by the mechanisms documented in the 40 and 50 series notebooks. This functionality uses your (potentially) unlabeled data to make predictions to classify your particles into your desired classes. An example will be provided in the 61-inference-demo.html document provided.

1.1 Pre-requisites for out-of-the-box usage

This code requires the following:

  • Data location: PartAn data should be stored on Box, and accessible via Box Sync (Box mounted locally on your computer). You can find more information about this here: https://support.box.com/hc/en-us/articles/360043697194-Installing-Box-Sync . The expected file format is a csv. If your data is not stored in this location, you can overwrite the name of the calculated filename in the code below.
  • Model data: This code assumes:
    • You’ve stored your models in an RData subdirectory of your main directory on Box and in an .RData format, with name modelprefix_modeling_info.RData. Your modelprefix will be used heavily here, so pick something descriptive. Good examples discern the models (e.g., glmnet, nb). See below for details.
    • Your saved final tidymodels workflow is named modelprefix_final_fit within your .RData file.

The location of the data is relatively flexible, but currently, the model storage format on Box and naming conventions are inflexible. It is recommended to adhere to these suggestions unless you’re skilled with R and can identify what you need to change within the code.

1.2 Using this code

In the options section below, you’ll see provided some empty areas for where you’ll add the filepath your data and models. Make sure to fill these in along with any other options indicated.

Then, Run All Chunks. The results will be displayed inline. After the results are produced, save this file, which will automatically be converted into an html document with the name 61-inference.nb.html.

1.3 Import relevant source code and packages

You shouldn’t need to make any changes here.

2 Options

Set your filepath and class prediction settings here. Some examples are provided.

#box directory (e.g., 'Volcano_Project' or 'DSI_AncientArtifacts', etc.)
box_directory <- 'DSI_AncientArtifacts'
#box_directory <- NULL

#data filepath (relative to box directory above, e.g. 'Archaeological Data/data.csv' or 'Archaeological Data/subdirectory/arch_data.csv', etc)
data_filepath <- NULL

#provide a list of the prefixes for your final model fits, e.g., 'glmnet', 'nb', etc.  This must be a list, even if it is one single model.
model_prefix_list <- list('glmnet', 'rf', 'xgb')
#model_prefix_list = []

#provide the string or value which corresponds to your target variable (e.g. microdebitage is 'exp')
target_class <- 'exp'
#target_class <- NULL

#provide the target column name if your data is labeled
target_colname <- 'particle_class'
#target_colname <- NULL

3 Load and validate data

In this section, the filepaths are calculated and the data is loaded. If you need to change filepaths here, you can do this by manually setting the project_files variable named list values (base_dir, data_path, and models_dir).

project_files <- set_inference_filepaths(box_directory, data_filepath)

# here's an example of how you can pass in specific filenames
# project_files['data_path'] <- 'LithicExperimentalData.csv'

# load and fix partAn file to dataframe
test_data <- load_data(project_files[['data_path']])

#here's an example of how you can directly write test data
test_data <- artifact_data

# validate data
validate_data(test_data)

4 Load models

In this step, we load all of the models from the model directory. If you need to set a different model directory, you can also do this here. However, you will want to make sure that your RData files within have the correct format (modelprefix_modeling_info.Rdata).

#using box path, load listed models
model_prefix_list %>%
  map(~load_model(., models_dir = project_files[['models_dir']]))

5 Get predictions using all loaded models

Here, we get the predictions from all of the models on the test data. You can peruse the results in the table below.

preds_list <- predict_with_models(model_prefix_list, test_data)
preds_list
$glmnet

$rf

$xgb
NA

6 Predict classes by model voting

In this section, we build the dataframe which will allow models to “cast votes” about the predicted class.

#build dataframe of joined predictions per sample (wide format for simple perusing)
preds_corr <- get_pred_correspondence(preds_list)
preds_corr

The following section explicitly produces the voting effect. The particle_matches list has 3 dataframes. The agreements dataframe lists all particles where all models completely agreed on the class. The disagreements dataframe shows where there was disagreements between models. For both of these dataframes, agreement_degree shows the extent to which models agreed (e.g. 0.5 for 2 models means that the models completely disagreed; 0.66 for 3 models means that 2 models agreed). mdl_class_pred shows the majority predicted class (given that an unequal number of models is present), or arbitrarily chooses a class if the votes are equally split.

particle_matches <- review_particles(preds_corr)
particle_matches[['agreements']]
particle_matches[['disagreements']]
NA

7 Identify target class particles and estimate sample composition

In this section, the particles which were classified as the target are extracted into their own dataframe. The percentage sample composition of these target particles is also reported.

target_particles <- extract_targets(particle_matches, target_class, agree_thresh = 0.6)
target_particles
#get target fraction
fraction_target <- nrow(target_particles)/nrow(test_data) 

#print fraction
print(str_c('Target class ', target_class, ' is predicted to compose: ', round(fraction_target, 5)*100, '% of your sample.'))
[1] "Target class exp is predicted to compose: 5.277% of your sample."
print(str_c('This corresponds to ', nrow(target_particles), ' out of ', nrow(test_data), ' particles.' ))
[1] "This corresponds to 2369 out of 44894 particles."

8 Identify misclassified particles

If you have labeled data where the particle type is known, use the following code to explore the misclassified particles. The full_misclass dataframe contains all of the misclassified particles, and the agree_misclass and disagree_misclass dataframes separate the full dataframe into where the models agreed vs. where they disagreed. The particles are organized by increasing probability of the target data for the first model type listed.

misclassified_samples <- NULL

if(!is.null(target_colname)){
  misclassified_samples <- get_misclassified(particle_matches, test_data, target_colname, target_class)
}

misclassified_samples[['full_misclass']]
misclassified_samples[['agree_misclass']]
misclassified_samples[['disagree_misclass']]
NA
LS0tDQp0aXRsZTogIjYxLWluZmVyZW5jZSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCg0KPiBBIGZyYW1ld29yayBmb3IgcHJlZGljdGluZyBhY3Jvc3MgbXVsdGlwbGUgbW9kZWxzDQoNCiMgT3ZlcnZpZXcgYW5kIFVzYWdlDQoNClRoaXMgbm90ZWJvb2sgcHJvdmlkZXMgaW5mZXJlbmNlIGZvciBQYXJ0QW4gZGF0YSwgYW5kIHVzZXMgeW91ciBidWlsdCBtb2RlbHMgYnkgdGhlIG1lY2hhbmlzbXMgZG9jdW1lbnRlZCBpbiB0aGUgNDAgYW5kIDUwIHNlcmllcyBub3RlYm9va3MuICBUaGlzIGZ1bmN0aW9uYWxpdHkgdXNlcyB5b3VyIChwb3RlbnRpYWxseSkgdW5sYWJlbGVkIGRhdGEgdG8gbWFrZSBwcmVkaWN0aW9ucyB0byBjbGFzc2lmeSB5b3VyIHBhcnRpY2xlcyBpbnRvIHlvdXIgZGVzaXJlZCBjbGFzc2VzLiAgQW4gZXhhbXBsZSB3aWxsIGJlIHByb3ZpZGVkIGluIHRoZSBgNjEtaW5mZXJlbmNlLWRlbW8uaHRtbGAgZG9jdW1lbnQgcHJvdmlkZWQuDQoNCiMjIFByZS1yZXF1aXNpdGVzIGZvciBvdXQtb2YtdGhlLWJveCB1c2FnZQ0KDQpUaGlzIGNvZGUgcmVxdWlyZXMgdGhlIGZvbGxvd2luZzoNCg0KKiAqKkRhdGEgbG9jYXRpb24qKjogIFBhcnRBbiBkYXRhIHNob3VsZCBiZSBzdG9yZWQgb24gQm94LCBhbmQgYWNjZXNzaWJsZSB2aWEgQm94IFN5bmMgKEJveCBtb3VudGVkIGxvY2FsbHkgb24geW91ciBjb21wdXRlcikuICBZb3UgY2FuIGZpbmQgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGlzIGhlcmU6IGh0dHBzOi8vc3VwcG9ydC5ib3guY29tL2hjL2VuLXVzL2FydGljbGVzLzM2MDA0MzY5NzE5NC1JbnN0YWxsaW5nLUJveC1TeW5jIC4gIFRoZSBleHBlY3RlZCBmaWxlIGZvcm1hdCBpcyBhIGNzdi4gIElmIHlvdXIgZGF0YSBpcyBub3Qgc3RvcmVkIGluIHRoaXMgbG9jYXRpb24sIHlvdSBjYW4gb3ZlcndyaXRlIHRoZSBuYW1lIG9mIHRoZSBjYWxjdWxhdGVkIGZpbGVuYW1lIGluIHRoZSBjb2RlIGJlbG93Lg0KKiAqKk1vZGVsIGRhdGEqKjogVGhpcyBjb2RlIGFzc3VtZXM6DQogICogWW91J3ZlIHN0b3JlZCB5b3VyIG1vZGVscyBpbiBhbiBgUkRhdGFgIHN1YmRpcmVjdG9yeSBvZiB5b3VyIG1haW4gZGlyZWN0b3J5IG9uIEJveCBhbmQgaW4gYW4gYC5SRGF0YWAgZm9ybWF0LCB3aXRoIG5hbWUgX21vZGVscHJlZml4X1xfbW9kZWxpbmdfaW5mby5SRGF0YS4gIFlvdXIgX21vZGVscHJlZml4XyB3aWxsIGJlIHVzZWQgaGVhdmlseSBoZXJlLCBzbyBwaWNrIHNvbWV0aGluZyBkZXNjcmlwdGl2ZS4gIEdvb2QgZXhhbXBsZXMgZGlzY2VybiB0aGUgbW9kZWxzIChlLmcuLCBgZ2xtbmV0YCwgYG5iYCkuICBTZWUgYmVsb3cgZm9yIGRldGFpbHMuDQogICogWW91ciBzYXZlZCBmaW5hbCB0aWR5bW9kZWxzIHdvcmtmbG93IGlzIG5hbWVkIF9tb2RlbHByZWZpeF9cX2ZpbmFsX2ZpdCB3aXRoaW4geW91ciAuUkRhdGEgZmlsZS4NCg0KVGhlIGxvY2F0aW9uIG9mIHRoZSBkYXRhIGlzIHJlbGF0aXZlbHkgZmxleGlibGUsIGJ1dCBjdXJyZW50bHksIHRoZSBtb2RlbCBzdG9yYWdlIGZvcm1hdCBvbiBCb3ggYW5kIG5hbWluZyBjb252ZW50aW9ucyBhcmUgaW5mbGV4aWJsZS4gIEl0IGlzIHJlY29tbWVuZGVkIHRvIGFkaGVyZSB0byB0aGVzZSBzdWdnZXN0aW9ucyB1bmxlc3MgeW91J3JlIHNraWxsZWQgd2l0aCBSIGFuZCBjYW4gaWRlbnRpZnkgd2hhdCB5b3UgbmVlZCB0byBjaGFuZ2Ugd2l0aGluIHRoZSBjb2RlLg0KICANCg0KIyMgVXNpbmcgdGhpcyBjb2RlDQpJbiB0aGUgb3B0aW9ucyBzZWN0aW9uIGJlbG93LCB5b3UnbGwgc2VlIHByb3ZpZGVkIHNvbWUgZW1wdHkgYXJlYXMgZm9yIHdoZXJlIHlvdSdsbCBhZGQgdGhlIGZpbGVwYXRoIHlvdXIgZGF0YSBhbmQgbW9kZWxzLiAgTWFrZSBzdXJlIHRvIGZpbGwgdGhlc2UgaW4gYWxvbmcgd2l0aCBhbnkgb3RoZXIgb3B0aW9ucyBpbmRpY2F0ZWQuDQoNClRoZW4sIFJ1biBBbGwgQ2h1bmtzLiAgVGhlIHJlc3VsdHMgd2lsbCBiZSBkaXNwbGF5ZWQgaW5saW5lLiAgQWZ0ZXIgdGhlIHJlc3VsdHMgYXJlIHByb2R1Y2VkLCBzYXZlIHRoaXMgZmlsZSwgd2hpY2ggd2lsbCBhdXRvbWF0aWNhbGx5IGJlIGNvbnZlcnRlZCBpbnRvIGFuIGh0bWwgZG9jdW1lbnQgd2l0aCB0aGUgbmFtZSBgNjEtaW5mZXJlbmNlLm5iLmh0bWxgLg0KDQojIyBJbXBvcnQgcmVsZXZhbnQgc291cmNlIGNvZGUgYW5kIHBhY2thZ2VzDQpZb3Ugc2hvdWxkbid0IG5lZWQgdG8gbWFrZSBhbnkgY2hhbmdlcyBoZXJlLg0KYGBge3IgbG9hZCBwYWNrYWdlcyBhbmQgc291cmNlLCByZXN1bHRzPSdoaWRlJ30NCnNvdXJjZShrbml0cjo6cHVybCgiNjAtaW5mZXJlbmNlLWhlbHBlcnMuUm1kIikpDQpmczo6ZmlsZV9kZWxldGUoIjYwLWluZmVyZW5jZS1oZWxwZXJzLlIiKQ0KYGBgDQoNCg0KIyBPcHRpb25zDQpTZXQgeW91ciBmaWxlcGF0aCBhbmQgY2xhc3MgcHJlZGljdGlvbiBzZXR0aW5ncyBoZXJlLiAgU29tZSBleGFtcGxlcyBhcmUgcHJvdmlkZWQuDQpgYGB7ciBzZXQgaW5mZXJlbmNlIG9wdGlvbnN9DQojYm94IGRpcmVjdG9yeSAoZS5nLiwgJ1ZvbGNhbm9fUHJvamVjdCcgb3IgJ0RTSV9BbmNpZW50QXJ0aWZhY3RzJywgZXRjLikNCmJveF9kaXJlY3RvcnkgPC0gJ0RTSV9BbmNpZW50QXJ0aWZhY3RzJw0KI2JveF9kaXJlY3RvcnkgPC0gTlVMTA0KDQojZGF0YSBmaWxlcGF0aCAocmVsYXRpdmUgdG8gYm94IGRpcmVjdG9yeSBhYm92ZSwgZS5nLiAnQXJjaGFlb2xvZ2ljYWwgRGF0YS9kYXRhLmNzdicgb3IgJ0FyY2hhZW9sb2dpY2FsIERhdGEvc3ViZGlyZWN0b3J5L2FyY2hfZGF0YS5jc3YnLCBldGMpDQpkYXRhX2ZpbGVwYXRoIDwtIE5VTEwNCg0KI3Byb3ZpZGUgYSBsaXN0IG9mIHRoZSBwcmVmaXhlcyBmb3IgeW91ciBmaW5hbCBtb2RlbCBmaXRzLCBlLmcuLCAnZ2xtbmV0JywgJ25iJywgZXRjLiAgVGhpcyBtdXN0IGJlIGEgbGlzdCwgZXZlbiBpZiBpdCBpcyBvbmUgc2luZ2xlIG1vZGVsLg0KbW9kZWxfcHJlZml4X2xpc3QgPC0gbGlzdCgnZ2xtbmV0JywgJ3JmJywgJ3hnYicpDQojbW9kZWxfcHJlZml4X2xpc3QgPSBbXQ0KDQojcHJvdmlkZSB0aGUgc3RyaW5nIG9yIHZhbHVlIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHlvdXIgdGFyZ2V0IHZhcmlhYmxlIChlLmcuIG1pY3JvZGViaXRhZ2UgaXMgJ2V4cCcpDQp0YXJnZXRfY2xhc3MgPC0gJ2V4cCcNCiN0YXJnZXRfY2xhc3MgPC0gTlVMTA0KDQojcHJvdmlkZSB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lIGlmIHlvdXIgZGF0YSBpcyBsYWJlbGVkDQp0YXJnZXRfY29sbmFtZSA8LSAncGFydGljbGVfY2xhc3MnDQojdGFyZ2V0X2NvbG5hbWUgPC0gTlVMTA0KDQpgYGANCg0KIyBMb2FkIGFuZCB2YWxpZGF0ZSBkYXRhIA0KSW4gdGhpcyBzZWN0aW9uLCB0aGUgZmlsZXBhdGhzIGFyZSBjYWxjdWxhdGVkIGFuZCB0aGUgZGF0YSBpcyBsb2FkZWQuICBJZiB5b3UgbmVlZCB0byBjaGFuZ2UgZmlsZXBhdGhzIGhlcmUsIHlvdSBjYW4gZG8gdGhpcyBieSBtYW51YWxseSBzZXR0aW5nIHRoZSBgcHJvamVjdF9maWxlc2AgdmFyaWFibGUgbmFtZWQgbGlzdCB2YWx1ZXMgKGBiYXNlX2RpcmAsIGBkYXRhX3BhdGhgLCBhbmQgYG1vZGVsc19kaXJgKS4NCg0KYGBge3IgbG9hZCBhbmQgdmFsaWRhdGUgZGF0YSBjc3ZzfQ0KcHJvamVjdF9maWxlcyA8LSBzZXRfaW5mZXJlbmNlX2ZpbGVwYXRocyhib3hfZGlyZWN0b3J5LCBkYXRhX2ZpbGVwYXRoKQ0KDQojIGhlcmUncyBhbiBleGFtcGxlIG9mIGhvdyB5b3UgY2FuIHBhc3MgaW4gc3BlY2lmaWMgZmlsZW5hbWVzDQojIHByb2plY3RfZmlsZXNbJ2RhdGFfcGF0aCddIDwtICdMaXRoaWNFeHBlcmltZW50YWxEYXRhLmNzdicNCg0KIyBsb2FkIGFuZCBmaXggcGFydEFuIGZpbGUgdG8gZGF0YWZyYW1lDQp0ZXN0X2RhdGEgPC0gbG9hZF9kYXRhKHByb2plY3RfZmlsZXNbWydkYXRhX3BhdGgnXV0pDQoNCiNoZXJlJ3MgYW4gZXhhbXBsZSBvZiBob3cgeW91IGNhbiBkaXJlY3RseSB3cml0ZSB0ZXN0IGRhdGENCnRlc3RfZGF0YSA8LSBhcnRpZmFjdF9kYXRhDQoNCiMgdmFsaWRhdGUgZGF0YQ0KdmFsaWRhdGVfZGF0YSh0ZXN0X2RhdGEpDQpgYGANCg0KIyBMb2FkIG1vZGVscw0KSW4gdGhpcyBzdGVwLCB3ZSBsb2FkIGFsbCBvZiB0aGUgbW9kZWxzIGZyb20gdGhlIG1vZGVsIGRpcmVjdG9yeS4gIElmIHlvdSBuZWVkIHRvIHNldCBhIGRpZmZlcmVudCBtb2RlbCBkaXJlY3RvcnksIHlvdSBjYW4gYWxzbyBkbyB0aGlzIGhlcmUuICBIb3dldmVyLCB5b3Ugd2lsbCB3YW50IHRvIG1ha2Ugc3VyZSB0aGF0IHlvdXIgUkRhdGEgZmlsZXMgd2l0aGluIGhhdmUgdGhlIGNvcnJlY3QgZm9ybWF0IChfbW9kZWxwcmVmaXhfXF9tb2RlbGluZ19pbmZvLlJkYXRhKS4NCmBgYHtyIGxvYWQgbW9kZWxzfQ0KI3VzaW5nIGJveCBwYXRoLCBsb2FkIGxpc3RlZCBtb2RlbHMNCm1vZGVsX3ByZWZpeF9saXN0ICU+JQ0KICBtYXAofmxvYWRfbW9kZWwoLiwgbW9kZWxzX2RpciA9IHByb2plY3RfZmlsZXNbWydtb2RlbHNfZGlyJ11dKSkNCmBgYA0KDQoNCiMgR2V0IHByZWRpY3Rpb25zIHVzaW5nIGFsbCBsb2FkZWQgbW9kZWxzDQpIZXJlLCB3ZSBnZXQgdGhlIHByZWRpY3Rpb25zIGZyb20gYWxsIG9mIHRoZSBtb2RlbHMgb24gdGhlIHRlc3QgZGF0YS4gIFlvdSBjYW4gcGVydXNlIHRoZSByZXN1bHRzIGluIHRoZSB0YWJsZSBiZWxvdy4NCmBgYHtyIHByZWRpY3Qgb24gdGhlIGRhdGEgdXNpbmcgdGhlIHByb3ZpZGVkIG1vZGVsc30NCnByZWRzX2xpc3QgPC0gcHJlZGljdF93aXRoX21vZGVscyhtb2RlbF9wcmVmaXhfbGlzdCwgdGVzdF9kYXRhKQ0KcHJlZHNfbGlzdA0KYGBgDQoNCiMgUHJlZGljdCBjbGFzc2VzIGJ5IG1vZGVsIHZvdGluZyANCkluIHRoaXMgc2VjdGlvbiwgd2UgYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGljaCB3aWxsIGFsbG93IG1vZGVscyB0byAiY2FzdCB2b3RlcyIgYWJvdXQgdGhlIHByZWRpY3RlZCBjbGFzcy4gIA0KYGBge3IgaWRlbnRpZnkgcXVlc3Rpb25hYmxlIHBhcnRpY2xlc30NCiNidWlsZCBkYXRhZnJhbWUgb2Ygam9pbmVkIHByZWRpY3Rpb25zIHBlciBzYW1wbGUgKHdpZGUgZm9ybWF0IGZvciBzaW1wbGUgcGVydXNpbmcpDQpwcmVkc19jb3JyIDwtIGdldF9wcmVkX2NvcnJlc3BvbmRlbmNlKHByZWRzX2xpc3QpDQpwcmVkc19jb3JyDQpgYGANClRoZSBmb2xsb3dpbmcgc2VjdGlvbiBleHBsaWNpdGx5IHByb2R1Y2VzIHRoZSB2b3RpbmcgZWZmZWN0LiAgVGhlIGBwYXJ0aWNsZV9tYXRjaGVzYCBsaXN0IGhhcyAzIGRhdGFmcmFtZXMuICBUaGUgYGFncmVlbWVudHNgIGRhdGFmcmFtZSBsaXN0cyBhbGwgcGFydGljbGVzIHdoZXJlIGFsbCBtb2RlbHMgY29tcGxldGVseSBhZ3JlZWQgb24gdGhlIGNsYXNzLiAgVGhlIGBkaXNhZ3JlZW1lbnRzYCBkYXRhZnJhbWUgc2hvd3Mgd2hlcmUgdGhlcmUgd2FzIGRpc2FncmVlbWVudHMgYmV0d2VlbiBtb2RlbHMuICBGb3IgYm90aCBvZiB0aGVzZSBkYXRhZnJhbWVzLCBgYWdyZWVtZW50X2RlZ3JlZWAgc2hvd3MgdGhlIGV4dGVudCB0byB3aGljaCBtb2RlbHMgYWdyZWVkIChlLmcuIDAuNSBmb3IgMiBtb2RlbHMgbWVhbnMgdGhhdCB0aGUgbW9kZWxzIGNvbXBsZXRlbHkgZGlzYWdyZWVkOyAwLjY2IGZvciAzIG1vZGVscyBtZWFucyB0aGF0IDIgbW9kZWxzIGFncmVlZCkuICBgbWRsX2NsYXNzX3ByZWRgIHNob3dzIHRoZSBtYWpvcml0eSBwcmVkaWN0ZWQgY2xhc3MgKGdpdmVuIHRoYXQgYW4gdW5lcXVhbCBudW1iZXIgb2YgbW9kZWxzIGlzIHByZXNlbnQpLCBvciBhcmJpdHJhcmlseSBjaG9vc2VzIGEgY2xhc3MgaWYgdGhlIHZvdGVzIGFyZSBlcXVhbGx5IHNwbGl0LiANCg0KYGBge3IgaWRlbnRpZnkgbWF0Y2hlZCBhbmQgbWlzbWF0Y2hlZCBwYXJ0aWNsZXMgYWNyb3NzIG1vZGVsc30NCnBhcnRpY2xlX21hdGNoZXMgPC0gcmV2aWV3X3BhcnRpY2xlcyhwcmVkc19jb3JyKQ0KcGFydGljbGVfbWF0Y2hlc1tbJ2FncmVlbWVudHMnXV0NCnBhcnRpY2xlX21hdGNoZXNbWydkaXNhZ3JlZW1lbnRzJ11dDQoNCmBgYA0KDQojIElkZW50aWZ5IHRhcmdldCBjbGFzcyBwYXJ0aWNsZXMgYW5kIGVzdGltYXRlIHNhbXBsZSBjb21wb3NpdGlvbg0KSW4gdGhpcyBzZWN0aW9uLCB0aGUgcGFydGljbGVzIHdoaWNoIHdlcmUgY2xhc3NpZmllZCBhcyB0aGUgdGFyZ2V0IGFyZSBleHRyYWN0ZWQgaW50byB0aGVpciBvd24gZGF0YWZyYW1lLiAgVGhlIHBlcmNlbnRhZ2Ugc2FtcGxlIGNvbXBvc2l0aW9uIG9mIHRoZXNlIHRhcmdldCBwYXJ0aWNsZXMgaXMgYWxzbyByZXBvcnRlZC4NCg0KYGBge3IgZXh0cmFjdGluZyB0YXJnZXQgcGFydGljbGVzIGZ1bmN0aW9ufQ0KdGFyZ2V0X3BhcnRpY2xlcyA8LSBleHRyYWN0X3RhcmdldHMocGFydGljbGVfbWF0Y2hlcywgdGFyZ2V0X2NsYXNzLCBhZ3JlZV90aHJlc2ggPSAwLjYpDQp0YXJnZXRfcGFydGljbGVzDQpgYGANCg0KYGBge3IgcHJpbnRpbmcgYW1vdW50IG9mIHRhcmdldCBwYXJ0aWNsZXN9DQojZ2V0IHRhcmdldCBmcmFjdGlvbg0KZnJhY3Rpb25fdGFyZ2V0IDwtIG5yb3codGFyZ2V0X3BhcnRpY2xlcykvbnJvdyh0ZXN0X2RhdGEpIA0KDQojcHJpbnQgZnJhY3Rpb24NCnByaW50KHN0cl9jKCdUYXJnZXQgY2xhc3MgJywgdGFyZ2V0X2NsYXNzLCAnIGlzIHByZWRpY3RlZCB0byBjb21wb3NlOiAnLCByb3VuZChmcmFjdGlvbl90YXJnZXQsIDUpKjEwMCwgJyUgb2YgeW91ciBzYW1wbGUuJykpDQpwcmludChzdHJfYygnVGhpcyBjb3JyZXNwb25kcyB0byAnLCBucm93KHRhcmdldF9wYXJ0aWNsZXMpLCAnIG91dCBvZiAnLCBucm93KHRlc3RfZGF0YSksICcgcGFydGljbGVzLicgKSkNCmBgYA0KIyBJZGVudGlmeSBtaXNjbGFzc2lmaWVkIHBhcnRpY2xlcw0KDQpJZiB5b3UgaGF2ZSBsYWJlbGVkIGRhdGEgd2hlcmUgdGhlIHBhcnRpY2xlIHR5cGUgaXMga25vd24sIHVzZSB0aGUgZm9sbG93aW5nIGNvZGUgdG8gZXhwbG9yZSB0aGUgbWlzY2xhc3NpZmllZCBwYXJ0aWNsZXMuICBUaGUgYGZ1bGxfbWlzY2xhc3NgIGRhdGFmcmFtZSBjb250YWlucyBhbGwgb2YgdGhlIG1pc2NsYXNzaWZpZWQgcGFydGljbGVzLCBhbmQgdGhlIGBhZ3JlZV9taXNjbGFzc2AgYW5kIGBkaXNhZ3JlZV9taXNjbGFzc2AgZGF0YWZyYW1lcyBzZXBhcmF0ZSB0aGUgZnVsbCBkYXRhZnJhbWUgaW50byB3aGVyZSB0aGUgbW9kZWxzIGFncmVlZCB2cy4gd2hlcmUgdGhleSBkaXNhZ3JlZWQuICBUaGUgcGFydGljbGVzIGFyZSBvcmdhbml6ZWQgYnkgaW5jcmVhc2luZyBwcm9iYWJpbGl0eSBvZiB0aGUgdGFyZ2V0IGRhdGEgZm9yIHRoZSBmaXJzdCBtb2RlbCB0eXBlIGxpc3RlZC4NCmBgYHtyIGdldCBtaXNjbGFzc2lmaWVkIHBhcnRpY2xlc30NCm1pc2NsYXNzaWZpZWRfc2FtcGxlcyA8LSBOVUxMDQoNCmlmKCFpcy5udWxsKHRhcmdldF9jb2xuYW1lKSl7DQogIG1pc2NsYXNzaWZpZWRfc2FtcGxlcyA8LSBnZXRfbWlzY2xhc3NpZmllZChwYXJ0aWNsZV9tYXRjaGVzLCB0ZXN0X2RhdGEsIHRhcmdldF9jb2xuYW1lLCB0YXJnZXRfY2xhc3MpDQp9DQoNCm1pc2NsYXNzaWZpZWRfc2FtcGxlc1tbJ2Z1bGxfbWlzY2xhc3MnXV0NCm1pc2NsYXNzaWZpZWRfc2FtcGxlc1tbJ2FncmVlX21pc2NsYXNzJ11dDQptaXNjbGFzc2lmaWVkX3NhbXBsZXNbWydkaXNhZ3JlZV9taXNjbGFzcyddXQ0KDQpgYGANCg0K